/*
 * scheduler.c - Mokri
 * Contributors: xMine
 * (Any new Contributor may add himself/herself to the list)
 * Changes and fixes to be made: Any Task is in Ring0 => Kernel rights
 */

#include "main.h"
#include "structs.h"
#include "print.h"
#include "memory.h"
#include "idt.h"
#include "scheduler.h"

static volatile uint32_t scheduler_ticks = 0;

// Variablen für Timer
struct TMR_Eintrag timer[MAX_TIMERS];
int numTimer;
char scheduleTimer;

// Variablen für Multi-Threading
static int numThreads;
static unsigned int cur_pid = 1;
static struct task *first_task = NULL;
volatile struct task *current_task=NULL;

/* Wird von IRQ0 aufgerufen. (siehe idt_behandlung in header/idt.c)
 * Parameter: Keine
 * Rückgaben: Keine
 */
struct stack_frame* irq_schedule (struct stack_frame *cpu) {
	if (numThreads > 0) {
		if (current_task == NULL || current_task->ticks >= current_task->priority || 
			current_task->running == FALSE || current_task->destroy == TRUE
		) {
			if (current_task != NULL) { // Wenn es nicht der erste Thread überhaupt ist,
				current_task->sf = cpu; // Status speichern
			} else { // Dies ist der absolut erste Thread!
				current_task = first_task;
			}
			
			current_task->ticks = 0;
			
			if (current_task->destroy == TRUE) { // Soll der Task gelöscht werden?
				if (current_task->flags & TASK_FLAG_WAKEUP_PARENT) {
					struct task *t = GetTaskStruct (current_task->parent);
					if (t != NULL) { // Gibts den Eltern Prozess überhaupt noch?
						t->sf->ebx = current_task->retval; // Rückgabewert des Programms übergeben
						ResumeTask (current_task->parent); // Und den Elternprozess wieder starten
					}
				}
				
				RemoveTask (current_task);
				
				do {
					next_task ();
				} while (current_task->running == FALSE);
			}
			
			
			do {
				next_task ();
			} while (current_task->running == FALSE);
			
			// Ggf. das Programm auf seinen Speicher mappen
			if (current_task->vaddr > 0x0) {
				paging_map_range ((void*)current_task->paddr, current_task->size, (void*)current_task->vaddr,
						PAGE_FLAG_USER | PAGE_FLAG_WRITEABLE | PAGE_FLAG_PRESENT);
			}

			cpu = current_task->sf; // CPU Status wiederherstellen
		} else {
			current_task->ticks++; // Tick counter erh�hen
		}
	}

	scheduler_ticks++;

	return cpu;

}

/* Hilfsfunktion */

void inline next_task () {
	if (current_task->next == NULL) {
		current_task = first_task;
	} else {
		current_task = current_task->next;
	}
}

/* Wartet eine gewisse Zeispanne - Kleinste mögliche Zeit = INTERVAL_TIME
 * Parameter: Zeit in MS
 * Rückgaben: Keine
 */
void sleep (uint32_t time_in_ms) {
  uint32_t endzeit = scheduler_ticks + (time_in_ms/INTERVAL_TIME);
  while (scheduler_ticks < endzeit) {
    asm volatile ("nop");
  }
}

/* Initialisiert Multi-Threading
 * Parameter: Keine
 * Rückgaben: Keine
 */
void mt_init () {
	numThreads = 0;
}

/* Erstellt einen neuen Task
 * Parameter:
 * Rückgaben: Thread ID
 */
struct task *mt_createThread (void *entry, BOOL run) {
	unsigned char *stack, *user_stack;
	struct task *task = (struct task*)pmm_alloc (); // 4kb für eine 8 Byte Strukur...
	stack = (uint8_t*)pmm_alloc(); // Stackposition
	user_stack = (uint8_t*)pmm_alloc(); // Stack für den UserMode
	// CPU Zustand erstellen
	struct stack_frame new_state = {
		.eax = 0,
		.ebx = 0,
		.ecx = 0,
		.edx = 0,
		.esi = 0,
		.edi = 0,
		.ebp = 0,
		._esp = (uint32_t) user_stack + STACK_SIZE,
		.eip = (uint32_t) entry, // Einsprungspunkt
		.cs = 8, // Ring-3 = 24 ; Ring-0 = 8
		.ss = 0x20 | 0x03,
		.eflags = 0x202, // IRQs einschalten
	};

	struct stack_frame *state = (void*) (stack + STACK_SIZE - sizeof(new_state));
	*state = new_state;

	task->sf = state;
	task->next = first_task;
	task->stack = stack;
	task->userstack = user_stack;
	task->priority = PRIORITY_NORMAL;
	task->ticks = 0;
	task->running = run;
	task->destroy = FALSE;
	task->pid = cur_pid;
	task->vaddr = 0x0;
	task->paddr = (uint32_t)entry;
	task->size = 0x0;
	task->parent = 0x0;
	task->flags = 0x0;
	first_task = task;
	
	numThreads++;
	cur_pid++;
	return task;
}

/* Hält einen Task an
 * Parameter: PID
 * Rückgaben: Returnwert, falls ein FreezeTask durch »system« aufgerufen wird
 */
int FreezeTask (pid_t pid) {
	if (pid == GetRunningTaskPID()) { 		// Hat sich das Programm selber eingefroren?
		volatile struct task *ptr = current_task;
		ptr->running = FALSE;			// Sich selbst als gestoppt markieren
		
		int ret; 
		asm volatile   ("int $32\n"		// IRQ0 aufrufen, damit der Prozess unterbrochen wird
				:"=b"(ret));
		return ret; 				// Sollte der Task wieder aufgetaut werden, so setzt er hier seine Arbeit fort!
	} else {
		volatile struct task *ptr = first_task;
		while (ptr != NULL) {
			if (ptr->pid == pid) {
				ptr->running = FALSE;
				return 0;
			}
			ptr = ptr->next;
		}
		
		return -1;
	}
}

/* Setzt einen angehaltenen Task fort
 * Parameter: PID
 * Rückgaben: Keine
 */
void ResumeTask (pid_t pid) {
	struct task *ptr = first_task;
	while (ptr != NULL) {
		if (ptr->pid == pid) {
			ptr->running = TRUE;
			return;
		}
		ptr = ptr->next;
	}
}

/* Gibt die PID des aktuell laufenden Tasks zurück
 * Parameter: Keine
 * Rückgaben: PID
 */
pid_t GetRunningTaskPID () {
	return current_task->pid;
}

/* Markiert einen Task zur Löschung
 * Parameter: PID, Rückgabewert der main() Funktion
 * RÜckgaben: Keine
 */
void DestroyTask (pid_t pid, int retval) {
	struct task *ptr = first_task;
	while (ptr != NULL) {
		if (ptr->pid == pid) {
			ptr->destroy = TRUE;
			ptr->retval = retval;
			
			if (GetRunningTaskPID () == pid) { // Ein Task will sich selber beenden
				FreezeTask (pid);
			}
			
			return;
		}
		ptr = ptr->next;
	}
}

/* Löscht einen Task aus der Liste
 * Parameter: Task Struktur
 * Rückgaben: Keine
 */
void RemoveTask (volatile struct task *t) {
	pmm_free (t->stack); // Stacks freigeben
	pmm_free (t->userstack);
	
	if (t->argc > 0) {
		free (t->argv); // Ggf. die Argumenten Liste rauswerfen
	}
	
	struct task *ptr = first_task;
	while (ptr != NULL && ptr->next != t) { // Task in der Liste suchen
		ptr = ptr->next;
	}
	
	if (ptr == NULL) { // Der Erste Task in der Liste soll gelöscht werden
		first_task = t->next;
	} else {
		ptr->next = t->next; // Neue Position setzen
	}
	
	pmm_free ((void*)t); // Und die Struktur komplett ausm Ram putzen
}

/* Liefert einen Task struct zu einer PID zurück
 * Parameter: PID
 * Rückgaben: task Pointer
 */
struct task *GetTaskStruct (pid_t pid) {
	struct task *ptr = first_task;
	while (ptr != NULL) {
		if (ptr->pid == pid) {
			return ptr;
		}
		ptr = ptr->next;
	}

	return NULL;
}

/* Setzt die Argumenteliste (argc, argv)
 * Parameter: PID, Anzahl der Argumente, Argumentliste
 * Rückgaben: Keine
 */
void SetArgumentList (pid_t pid, int argc, char **argv) {
	struct task *t = GetTaskStruct (pid);
	
	if (t == NULL) {
		return;
	}
	
	t->argc = argc;
	t->argv = argv;
}

/* Gibt die Argumenteliste zurück (argc, argv)
 * Parameter: PID, Anzahl der Argumente (Zeiger)
 * Rückgaben: Keine
 */
char **GetArgumentList (pid_t pid, int *argc) {
	struct task *t = GetTaskStruct (pid);
	
	if (t == NULL) {
		*argc = 0;
		return NULL;
	}
	
	*argc = t->argc;
	return t->argv;
}
